package de.teamgamma.cansat.groundstation.livestream;

import info.monitorenter.gui.chart.Chart2D;
import info.monitorenter.gui.chart.IAxis.AxisTitle;
import info.monitorenter.gui.chart.ITrace2D;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.text.DefaultCaret;

import org.json.JSONException;
import org.json.JSONObject;

import de.teamgamma.cansat.groundstation.control.Controller;
import de.teamgamma.cansat.groundstation.options.OptionKeys;
import de.teamgamma.cansat.groundstation.transmitting.TransmitOptions;

/**
 * Controls the livestream
 * @author Marc Huisinga
 *
 */
public class Livestream {
	/**
	 * Seperator between pieces of data in the log files generated by the livestream
	 */
	public static final char DATASEPARATOR = ',';
	/**
	 * Seperator between the dimension and the unit in the log files generated by the livestream
	 */
	public static final char UNITSEPARATOR = '/';
	/**
	 * Value that is transmitted when the CanSat wasn't able to transmit a value
	 */
	public static final double EMPTYVALUE = 0;
	/**
	 * String that is generated in the logfiles if an EMPTYVALUE was transmitted
	 */
	public static final String EMPTYPLACEHOLDER = "###";
	/**
	 * Time between each debug json-generation
	 */
	public static final int DEBUGGENERATIONDELAY = 500;
	
	private boolean streamPaused;
	private boolean streamStopped;
	private boolean logData;
	private LivestreamData data;
	private File outputFile;
	private LivestreamPanel relatedPanel;
	private Timer timer;
	private TimerTask task;
	private Chart2D relatedChart;
	private ITrace2D mainTrace;
	private String currentXAxisValue;
	private String currentYAxisValue;
	private String currentXAxisName;
	private String currentYAxisName;
	private BufferedWriter dataStream;
	private String[] cardinalDirections;
	private TransmitOptions transmitOptions;

	/**
	 * 
	 * @param relatedLivestreamPanel
	 */
	public Livestream(LivestreamPanel relatedLivestreamPanel) {
		streamPaused = false;
		streamStopped = false;
		cardinalDirections = new String[4];
		cardinalDirections[0] = "N";
		cardinalDirections[1] = "E";
		cardinalDirections[2] = "S";
		cardinalDirections[3] = "W";
		relatedPanel = relatedLivestreamPanel;
		relatedChart = relatedPanel.getChart();
		mainTrace = relatedPanel.getTrace();
		transmitOptions = TransmitOptions.getInstance();
		data = new LivestreamData();
		data.setLimitedFrame(true);
		data.setFrameSize(LivestreamPanel.TRACERMAXVALUES);
		if (Controller.getInstance().getOptions().getOptionValue(OptionKeys.LOGDATA).equals("1")) {
			logData = true;
		} else {
			logData = false;
		}
	}
	
	/**
	 * Starts the debug stream
	 */
	public void startDebugStream() {
		startDataStream();
		timer = new Timer();
	    task = new TimerTask(){
	    	@Override
	    	public void run() {
	    		Random random = new Random();
	    		JSONObject json = new JSONObject();
	    		for (String currentKey : TransmittedData.ALLKEYS) {
	    			if (currentKey.equals(TransmittedData.LATITUDE.getKey())) {
	    				//DDDMM.MMMMNWSE
	    				json.put(currentKey, generateRandomStringDigits(5,random) + "." + generateRandomStringDigits(4,random) + generateRandomCardinalDirection(random));
	    			} else if (currentKey.equals(TransmittedData.LONGITUDE.getKey())) {
	    				//DDMM.MMMMNWSE
	    				json.put(currentKey, generateRandomStringDigits(4,random) + "." + generateRandomStringDigits(4,random) + generateRandomCardinalDirection(random));
	    			} else {
	    				long value = random.nextInt();
		    			if (value > 0) {
		    				json.put(currentKey, value);
		    			} else {
		    				json.put(currentKey, -1*value);
		    			}
	    			}
	    		}
	    		update(json);
	    	}
	    };
	    timer.schedule(task, 1000, DEBUGGENERATIONDELAY);
	}
	
	private String generateRandomStringDigits(int length, Random random) {
		String digits = "";
		for (int i = 0; i != length; i++) {
			digits += String.valueOf(random.nextInt(10));
		}
		return digits;
	}
	
	private String generateRandomCardinalDirection(Random random) {
		return cardinalDirections[random.nextInt(cardinalDirections.length)];
	}
	
	
	/**
	 * Starts the stream
	 */
	public void startStream() {
		startDataStream();
		transmitOptions.setSendToGroundstation(true);
	}
	
	/**
	 * Stops the debug stream
	 */
	public void stopDebugStream() {
		timer.cancel();
		timer.purge();
		closeDataStream();
	}
	
	/**
	 * Stops the stream
	 */
	public void stopStream() {
		closeDataStream();
		transmitOptions.setSendToGroundstation(false);
	}
	
	/**
	 * Updates the livestream with a JSONObject
	 * @param json - JSONObject
	 */
	public void update(JSONObject json) {
		if (!streamStopped) {
			if (!streamPaused) {
				if (json.getDouble(currentXAxisValue) != EMPTYVALUE && json.getDouble(currentYAxisValue) != EMPTYVALUE) {
					mainTrace.addPoint(json.getDouble(currentXAxisValue), json.getDouble(currentYAxisValue));
					relatedPanel.getDisplayArea().append("(" + json.getDouble(currentXAxisValue) + " | " + json.getDouble(currentYAxisValue) + ")" + System.getProperty("line.separator"));
				}
			}
			if (logData) {
				try {
					for (String key : TransmittedData.ALLKEYS) {
						if (TransmittedData.STRINGKEYS.contains(key)) {
							if (json.getString(key) != String.valueOf(EMPTYVALUE)) {
								dataStream.write(json.getString(key) + DATASEPARATOR);	
							} else {
								dataStream.write(EMPTYPLACEHOLDER + DATASEPARATOR);
							}
						} else {
							double value = json.getDouble(key);
							if (value != EMPTYVALUE) {
								dataStream.write(new BigDecimal(json.getDouble(key)).toPlainString() + DATASEPARATOR);	
							} else {
								dataStream.write(EMPTYPLACEHOLDER + DATASEPARATOR);
							}
						}
						
					}
					dataStream.write(System.getProperty("line.separator"));
				} catch (JSONException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			data.updateDataWithJson(json);
		}
	}
	
	private void closeDataStream() {
		if (logData) {
			try {
				dataStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void refreshData() {
		mainTrace.removeAllPoints();
		relatedPanel.getDisplayArea().setText(null);
		relatedPanel.getDisplayArea().append("("+currentXAxisName+"["+getUnitFromKey(currentXAxisValue) + "] | "+currentYAxisName+"[" + getUnitFromKey(currentYAxisValue) + "]):" + System.getProperty("line.separator"));
		String[] xData = data.getDataOfKey(currentXAxisValue);
		String[] yData = data.getDataOfKey(currentYAxisValue);
		
		if (xData != null && yData != null) {
			for (int index = 0; index != xData.length; index ++) {
				double xValue = Double.valueOf(xData[index]);
				double yValue = Double.valueOf(yData[index]);
				if (xValue != EMPTYVALUE && yValue != EMPTYVALUE) {
					mainTrace.addPoint(xValue, yValue);
					relatedPanel.getDisplayArea().append("(" + xValue + " | " + yValue + ")" + System.getProperty("line.separator"));
				}
			}
		}
	}
	
	private void startDataStream() {
		
		
		if (logData) {
			File saveDir = new File(Controller.getInstance().getOptions().getOptionValue(OptionKeys.EXPINLOCKEY));
			if (saveDir.mkdirs() || saveDir.exists()) {
				outputFile = new File(saveDir + "/" + new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss'.csl'").format(new Date()));
				try {
					outputFile.createNewFile();
					dataStream = new BufferedWriter(new FileWriter(outputFile));
					for (TransmittedValue currentValue : TransmittedData.ALLVALUES) {
						dataStream.write(currentValue.getKey() + UNITSEPARATOR + currentValue.getUnit() + DATASEPARATOR);
					}
					dataStream.write(System.getProperty("line.separator"));
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else {
				System.err.println("Selected logging location was not found!\nData is not going to be logged!");
				logData = false;
			}
		}
	}
	
	private void refreshScrollState() {
		((DefaultCaret)relatedPanel.getDisplayArea().getCaret()).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
	}
	
	private String getUnitFromKey(String key) {
		for (TransmittedValue currentValue : TransmittedData.ALLVALUES) {
			if (currentValue.getKey().equals(key)) {
				return currentValue.getUnit();
			}
		}
		return null;
	}
	
	/**
	 * Pauses / Unpauses the livestream (Still logs transmitted data)
	 */
	public void toggleStreamPaused() {
		if (streamPaused) {
			streamPaused = false;
			relatedPanel.getPause().setTranslationKey("livestream.LivestreamPanel.pause");
			refreshData();
		} else {
			streamPaused = true;
			relatedPanel.getPause().setTranslationKey("livestream.LivestreamPanel.unpause");
		}
		Controller.getInstance().getGuiController().getLanguages().translateSpecificComponent(relatedPanel.getPause());
		
		refreshScrollState();

	}
	
	/**
	 * Stops / Unstops the livestream (Doesn't log transmitted data)
	 */
	public void toggleStreamStopped() {
		if (streamStopped) {
			streamStopped = false;
			relatedPanel.getStop().setTranslationKey("livestream.LivestreamPanel.stop");
		} else {
			streamStopped = true;
			relatedPanel.getStop().setTranslationKey("livestream.LivestreamPanel.unstop");
		}
		Controller.getInstance().getGuiController().getLanguages().translateSpecificComponent(relatedPanel.getStop());
		
		refreshScrollState();
	}
	
	/**
	 * Gets the key of the current x axis selected
	 * @return key
	 */
	public String getCurrentXAxisValue() {
		return currentXAxisValue;
	}

	/**
	 * Sets the key of the current x axis selected
	 * @param currentXAxisValue
	 */
	public void setCurrentXAxisValue(String currentXAxisValue) {
		this.currentXAxisValue = currentXAxisValue;
		currentXAxisName = relatedPanel.getXAxisNameFromKey(currentXAxisValue);
		relatedChart.getAxisX().setAxisTitle(new AxisTitle(currentXAxisName + " | " + TransmittedData.getUnitFromKey(currentXAxisValue)));
		refreshData();
	}

	/**
	 * Gets the key of the current y axis selected
	 * @return key
	 */
	public String getCurrentYAxisValue() {
		return currentYAxisValue;
	}

	/**
	 * Sets the key of the current y axis selected
	 * @param currentYAxisValue
	 */
	public void setCurrentYAxisValue(String currentYAxisValue) {
		this.currentYAxisValue = currentYAxisValue;
		currentYAxisName = relatedPanel.getYAxisNameFromKey(currentYAxisValue);
		relatedChart.getAxisY().setAxisTitle(new AxisTitle(currentYAxisName + " | " + TransmittedData.getUnitFromKey(currentYAxisValue)));
		refreshData();
	}
}
